package cn.rayland.library.utils;

import android.content.Context;
import android.util.Log;
import android_serialport_api.SerialPort;
import com.alibaba.fastjson.JSON;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import cn.rayland.app.yandktest.Machine;
import cn.rayland.app.yandktest.X3gStatus;
import cn.rayland.app.yandktest.gpx;
import cn.rayland.library.bean.FileTask;
import cn.rayland.library.bean.GcodeTask;
import cn.rayland.library.bean.MachineTask;
import cn.rayland.library.bean.SeniorSetting;
import cn.rayland.library.bean.MachineState;
import cn.rayland.library.bean.SliceSetting;
import cn.rayland.library.stm32.IIC;
import cn.rayland.library.utils.ConvertUtils.Callback;

/**
 * Created by gw on 2015-07-17.
 */
public class MachineManager {
	private static final String TAG = MachineManager.class.getSimpleName();
	private static MachineManager instance;
	private Context context;
	private volatile MachineState state;
	private long startTime;
	private IIC stm32;
	private static final int x3gChunk = 10;
	private volatile boolean isCancel = true;
	public MachineTask machineTask;
	public volatile Machine machine;
	public X3gStatus x3gStatus;
	private volatile String customConfigPath;
	private ExecutorService threadExecutors;
	private ReadDataThread readDataThread;
	private SerialPort serialPort;
	private InputStream inputStream;
	private ReadSerialThread readSerialThread;
	
	public static MachineManager getInstance(Context context){
		if(instance == null){
			synchronized (MachineManager.class) {
				if(instance == null){
					instance = new MachineManager(context);
				}
			}
		}
		return instance;
	}
	
	private MachineManager(){}
	private MachineManager(Context context){
		this.context = context;
		this.stm32 = IIC.getInstance();
		this.state = new MachineState();
		threadExecutors = Executors.newFixedThreadPool(2);
		this.readDataThread = new ReadDataThread();
		initMachine();
		threadExecutors.submit(readDataThread);
		try {
			serialPort = new SerialPort(new File("/dev/ttyS1"), 115200, 0);
			inputStream = serialPort.getInputStream();
			readSerialThread = new ReadSerialThread();
			threadExecutors.submit(readSerialThread);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 执行任务
	 * 
	 * @param task
	 */
	public void sendTask(final MachineTask task, final boolean ifReset) {
		if (task == null) {
			Log.e(TAG, "task is null");
			return;
		}
		if (task instanceof GcodeTask) {
			execGcode((GcodeTask)task, ifReset);
		}

		else if (task instanceof FileTask) {
			String filePath = ((FileTask) task).getContent().getAbsolutePath();
			SliceSetting setting = ((FileTask) task).getSliceSetting();
			final File file = new File(filePath);
			if (!file.exists() || file.isDirectory()) {
				Log.e(TAG, "file not found");
				return;
			}
			if (!checkMachine(ifReset)) {
				return;
			}
			if (filePath.endsWith(".stl")) {
				ConvertUtils.stlToGcodeByCloud(file, setting, new Callback() {

					@Override
					public void onPreConvert() {
						if (task.getConvertCallback() != null) {
							task.getConvertCallback().onPreConvert(
									ConvertUtils.STL_TO_GCODE);
						}
					}

					@Override
					public void onConvertSuccess(final File file,
							X3gStatus x3gStatus) {
						if (task.getConvertCallback() != null) {
							task.getConvertCallback().onConvertSuccess(
									ConvertUtils.STL_TO_GCODE, x3gStatus);
						}
						((FileTask) task).setContent(file);
						sendTask(task, ifReset);
					}

					@Override
					public void onConvertFailed(String error) {
						Log.e(TAG, "convert stl to gcode failed");
						if (task.getConvertCallback() != null) {
							task.getConvertCallback().onConvertFailed(
									ConvertUtils.STL_TO_GCODE, error);
						}
					}

					@Override
					public void onConvertProgress(int type, int progress) {
						if (task.getConvertCallback() != null) {
							task.getConvertCallback().onConvertProgress(type,
									progress);
						}
					}
				});
				
//				ConvertUtils.stlToGcodeByLocal(file, setting, new Callback() {
//				
//				@Override
//				public void onPreConvert() {
//					if(task.getConvertCallback()!=null){
//						task.getConvertCallback().onPreConvert(ConvertUtils.STL_TO_GCODE);
//					}
//				}
//				
//				@Override
//				public void onConvertSuccess(final File file, X3gStatus x3gStatus) {
//							if(task.getConvertCallback()!=null){
//								task.getConvertCallback().onConvertSuccess(ConvertUtils.STL_TO_GCODE, x3gStatus);
//							}
//							((FileTask)task).setFilePath(file.getAbsolutePath());
//							sendTask(task, ifReset);
//				}
//				
//				@Override
//				public void onConvertFailed(String error) {
//					Log.e(TAG, "convert stl to gcode failed");
//					if(task.getConvertCallback()!=null){
//						task.getConvertCallback().onConvertFailed(ConvertUtils.STL_TO_GCODE, error);
//					}
//				}
//
//				@Override
//				public void onConvertProgress(int type, int progress) {
//					if(task.getConvertCallback()!=null){
//						task.getConvertCallback().onConvertProgress(type, progress);
//					}
//				}
//			});

			} else if (filePath.endsWith(".gcode")) {
				ConvertUtils.gcodeToX3g(file, machine, new Callback() {

					@Override
					public void onPreConvert() {
						if (task.getConvertCallback() != null) {
							task.getConvertCallback().onPreConvert(
									ConvertUtils.GCODE_TO_X3G);
						}
					}

					@Override
					public void onConvertSuccess(File file, X3gStatus consum) {
						x3gStatus = consum;
						x3gStatus.length = Math.round(x3gStatus.length
								/ machine.filamentDiameter
								* machine.nozzleDiameter / 10);
						if (task.getConvertCallback() != null) {
							task.getConvertCallback().onConvertSuccess(
									ConvertUtils.GCODE_TO_X3G, x3gStatus);
						}
						prepare(task, ifReset);
						new SendDataThread(file, ifReset).start();
					}

					@Override
					public void onConvertFailed(String error) {
						Log.e(TAG, "convert gcode to x3g failed");
						if (task.getConvertCallback() != null) {
							task.getConvertCallback().onConvertFailed(
									ConvertUtils.GCODE_TO_X3G, error);
						}
					}

					@Override
					public void onConvertProgress(int type, int progress) {
						if (task.getConvertCallback() != null) {
							task.getConvertCallback().onConvertProgress(type,
									progress);
						}
					}
				});

			} else if (filePath.endsWith(".x3g")) {
				prepare(task, ifReset);
				new SendDataThread(file, ifReset).start();
			} else {
				Log.e(TAG, "this format is not supported to execute");
				return;
			}
		}
	}

	/**
	 * 直接执行 GCODE字符串
	 * @param gcodeStr
	 * @param task
	 * @param reset
	 */
	private void execGcode(final GcodeTask task, final boolean ifReset) {
		String content= task.getContent();
		if (task == null || content == null) {
			Log.e(TAG, "task or gcodeStr is null");
			return;
		}
		if (!checkMachine(ifReset)){
			return;
		}
		ConvertUtils.gcodeToX3g(content, machine, new Callback() {

			@Override
			public void onPreConvert() {
				if(task.getConvertCallback()!=null){
					task.getConvertCallback().onPreConvert(ConvertUtils.STL_TO_GCODE);
				}
			}
			
			@Override
			public void onConvertSuccess(File file, X3gStatus consum) {
				x3gStatus = consum;
				x3gStatus.length = Math.round(x3gStatus.length / machine.filamentDiameter * machine.nozzleDiameter / 10);
				if(task.getConvertCallback()!=null){
					task.getConvertCallback().onConvertSuccess(ConvertUtils.GCODE_TO_X3G, x3gStatus);
				}
				prepare(null, ifReset);
				
				new SendDataThread(file, ifReset).start();
			}

			@Override
			public void onConvertFailed(String error) {
				Log.e(TAG, "convert gcode to x3g failed");
				if(task.getConvertCallback()!=null){
					task.getConvertCallback().onConvertFailed(ConvertUtils.GCODE_TO_X3G, error);
				}
			}

			@Override
			public void onConvertProgress(int type, int progress) {
				if(task.getConvertCallback()!=null){
					task.getConvertCallback().onConvertProgress(type, progress);
				}
			}
		});
	}
	
	/**
	 * 检查机器
	 * @return
	 */
	private boolean checkMachine(boolean ifReset) {
		boolean checkPass = true;
		if (stm32 == null || !stm32.haveDevice()) {
			checkPass = false;
			Log.e(TAG, "stm32 not found");
		}
		if (!isCancel) {
			checkPass = false;
			Log.e(TAG, "stm32 is busy");
		}
		initMachine();
		if(ifReset){
			gpx.gpxInit(machine);
		}
		return checkPass;
	}

	/**
	 * 开始前的准备工作
	 */
	private void prepare(MachineTask task, boolean reset) { 
		machineTask = task;
		isCancel = false;
		startTime = System.currentTimeMillis();
		if (reset) {
			byte[] byte2 = new byte[2];
			byte2[0] = 8;
			byte2[1] = 0;
			stm32.sendData(byte2, 0, byte2.length);
			byte2 = null;
			Log.d(TAG, "reset");
		}
		if(machineTask.getTaskCallback() != null){
			machineTask.getTaskCallback().onStart(task, x3gStatus);
		}
	}

	/**
	 * 取消打印
	 */
	public void cancel(boolean force) {
		isCancel = true;
		x3gStatus = null;
		if (force) {
			byte[] byte2 = new byte[2];
			byte2[0] = 1;
			byte2[1] = 0;
			stm32.sendData(byte2, 0, byte2.length);
			byte2 = null;
			gpx.gpxInit(machine);
			Log.d(TAG, "stop");
			if(machineTask.getTaskCallback() != null){
				machineTask.getTaskCallback().onCancel(machineTask, state);
			}
		}
	}

	/**
	 * 读取机器状态
	 */
	private void readDataFromMachine() {
		byte[] data = stm32.readData(7);
		state.setTask(machineTask);

		if (data != null) {
			if ((data[3] & (1 << 1)) != 0) {
//				float distance = (FormatUtils.byte2short(new byte[] { data[5],
//						data[6] }) + 10000) / 1000f;
				int a = data[6]&0xff;
				int b = data[5]&0xff;
				float distance = (a*256+b+10000)/ 1000f;
				
				if (machineTask.getGcodeCallback() != null) {
					machineTask.getGcodeCallback().onM900(distance);
				}
				byte[] byte2 = new byte[2];
				byte2[0] = 0;
				byte2[1] = 2;
				stm32.sendData(byte2, 0, byte2.length);
			} else {
				state.setLastlinenum(FormatUtils.byte2short(new byte[] {
						data[5], data[6] }) & 0x0FFFF);
			}
			state.setVal(FormatUtils
					.byte2short(new byte[] { data[0], data[1] }) & 0x0FFFF);
			state.setTemper((new BigInteger(1, new byte[] { data[2] })
					.intValue()) * 2);
			state.setLastlinenum(FormatUtils.byte2short(new byte[] { data[5],
					data[6] }) & 0x0FFFF);
			state.setWorking((data[4] & 1) != 0);
			state.setMoving((data[4] & (1 << 1)) != 0);
			state.setExtruding((data[4] & (1 << 2)) != 0);
			state.setHeating(new boolean[] { (data[4] & (1 << 3)) != 0,
					(data[4] & (1 << 4)) != 0 });
			state.setFanning(new boolean[] { (data[4] & (1 << 5)) != 0,
					(data[4] & (1 << 6)) != 0 });
			state.setError((data[4] & (1 << 7)) != 0);
			state.setUsedTime(System.currentTimeMillis() - startTime);

			data = null;
		}
	}

	/**
	 * 向机器发送数据
	 */
	private void sendDataToMachine(File file) {
		FileInputStream fis;
		try {
			fis = new FileInputStream(file);
			byte[] tmpBytes = new byte[x3gChunk + 2];
			int length = 0;
			while (!isCancel) {
				readDataFromMachine();
				if(state.isError()){
					Log.e(TAG, "STM32 ERROR");
					if(machineTask.getTaskCallback() != null){
						machineTask.getTaskCallback().onError(machineTask, state);
					}
					cancel(true);
				}else if (state.getVal() > x3gChunk) {
					if ((length = fis.read(tmpBytes, 2, x3gChunk)) != -1) {
						tmpBytes[0] = 0;
						tmpBytes[1] = 0;
						stm32.sendData(tmpBytes, 0, length + 2);
						state.setProgress((int)((1-(fis.available()*1.0f/fis.getChannel().size())) * 100));
					} 
					else {
						if(!state.isWorking()) {
							cancel(false);
							if(machineTask.getTaskCallback() != null){
								machineTask.getTaskCallback().onFinished(machineTask, state);
							}
						}
					}
				}
			}
			tmpBytes = null;
			fis.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 发送initX3g初始化脚本
	 */
	private void sendInitX3gData() {
		byte[] initX3g = gpx.getInitX3g(machine);
		byte[] tmpBytes = new byte[x3gChunk + 2];
		ByteArrayInputStream bis = new ByteArrayInputStream(initX3g);
		int length = 0;
		while (bis.available() > 0) {
			readDataFromMachine();
			if (state.getVal() > x3gChunk) {
				if ((length = bis.read(tmpBytes, 2, x3gChunk)) != -1) {
					tmpBytes[0] = 0;
					tmpBytes[1] = 0;
					stm32.sendData(tmpBytes, 0, length + 2);
				}
			}
		}
		try {
			bis.close();
			tmpBytes = null;
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * 发送数据Thread
	 * @author gw
	 *
	 */
	private class SendDataThread extends Thread {
		File file;
		boolean ifReset;
		public SendDataThread(File file, boolean ifReset) {
			this.file = file;
			this.ifReset = ifReset;
		}

		@Override
		public void run() {
			if (customConfigPath != null && ifReset) {
				sendInitX3gData();
			}
			sendDataToMachine(file);
		}
	}

	/**
	 * 获取机器配置参数
	 */
	public void initMachine() {
		try {
			InputStream fis = (customConfigPath == null) ? (context.getAssets()
					.open("machine.txt")) : (new FileInputStream(
					customConfigPath));
			String settings = IOUtils.readToString(fis);
			fis.close();
			List<SeniorSetting> seniorSettings = JSON.parseArray(settings,
					SeniorSetting.class);
			for (int i = 0; i < seniorSettings.size(); i++) {
				if (seniorSettings.get(i).isSelected()) {
					Machine newMachine = seniorSettings.get(i).getSetting();
					
					if(machine == null || !machine.equals(newMachine)){
						machine = newMachine;
						gpx.gpxInit(newMachine);
						new Thread(){
							public void run() {sendInitX3gData();};
						}.start();
						
					}
					
					break;
				}
				if (i == seniorSettings.size() - 1) {
					customConfigPath = null;
					initMachine();
					Log.e(TAG, "cause custom config not found,use default config");
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			customConfigPath = null;
		}
	}

	/**
	 * 保存上一次执行完的文件，保存为x3g格式
	 * @param dirPath
	 * @param fileName
	 */
	public boolean saveFinishedFile(String dirPath, String fileName){
		File file = new File(PathUtils.getGpxFolder(), ConvertUtils.TMP_X3G);
		if(file.exists() && file.isFile()){
			File dir = new File(dirPath);
			if(!dir.exists()&&dir.isDirectory()){
				dir.mkdirs();
			}
			return file.renameTo(new File(dirPath, fileName));
		}
		return false;
	}
	
	/**
	 * 设置机器参数
	 * @param configFilePath
	 */
	public boolean setCustomMachineConfig (String configFilePath){
		if(checkConfig(configFilePath)){
			customConfigPath = configFilePath;
			return true;
		}
		return false;
	}
	/**
	 * 检查设置的参数文件格式是否正确
	 * @param configFilePath
	 */
	private boolean checkConfig(String configFilePath) {
		try {
			String settings = IOUtils.readToString(new FileInputStream(configFilePath));
			List<SeniorSetting> seniorSettings = JSON.parseArray(
					settings, SeniorSetting.class);
			for (int i=0;i<seniorSettings.size();i++) {
				if (seniorSettings.get(i).isSelected()) {
					break;
				}
				if(i == seniorSettings.size()-1){
					Log.e(TAG, "selected config not found");
					return false;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}
	
	/**
	 * 获取当前机器状态
	 * @return
	 */
	public MachineState getMachineState(){
		return state;
	}

	class ReadDataThread extends Thread {
		@Override
		public void run() {
			while(true){
				if(isCancel){
					readDataFromMachine();
				}
			}
		}
	}
	/**
	 * 串口读线程
	 * @author gw
	 *
	 */
	class ReadSerialThread extends Thread {
		@Override
		public void run() {
			while(!isInterrupted()) {
				int size;
				try {
					byte[] xyzData = new byte[64];
					if (inputStream == null) return;
					size = inputStream.read(xyzData);
					if(size>0){
						float[] positions = new float[3];
						positions[0] = (float) (FormatUtils.byte2int(new byte[] {xyzData[0], xyzData[1], xyzData[2], xyzData[3]})/machine.axis_x.steps_per_mm);
						positions[1] = (float) (FormatUtils.byte2int(new byte[] {xyzData[4], xyzData[5], xyzData[6], xyzData[7]})/machine.axis_y.steps_per_mm);
						positions[2] = (float) (FormatUtils.byte2int(new byte[] {xyzData[8], xyzData[9], xyzData[10], xyzData[11]})/machine.axis_z.steps_per_mm);
						if(machineTask.getGcodeCallback()!=null){
							machineTask.getGcodeCallback().onM114(positions);
						}
						byte[] byte2 = new byte[2];
						byte2[0] = 0;
						byte2[1] = 1;
						stm32.sendData(byte2, 0, byte2.length);
					}
				} catch (IOException e) {
					e.printStackTrace();
					return;
				}
			}
		}
	}
}
